<script>
import {Table, TableColumn} from 'element-ui'
import {set, get, cloneDeep} from 'lodash'
import mixinOptionExtensions from './mixin-package-option.js'

const TYPES = ['selection', 'index', 'expand']
const SLOT_DEFAULT = 'default-'
const SLOT_HEADER = 'header-'
let len = 0
// 定义表头class类名
const HeaderCellClassName = 'editable-header-cell-class'
const RowClassName = 'editable-row-class'
// 表头不可更改的关键词，type值或者属性
const disableMoveTableArr = ['index', 'expand', 'selection']

/**
 * 转换为大小驼峰命名
 * abc-efg => abcEfg
 */
export const toCamelCase = str => {
  return str.indexOf('-') !== -1
    ? str.replace(/-([a-zA-Z])/g, ($0, $1) => $1.toUpperCase())
    : str
}

export default {
  name: 'Editable',
  components: {
    'el-table': Table,
    'el-table-column': TableColumn
  },
  directives: {
    drag: {
      bind(el, binding) {
        // 如果没有配置custom-attr属性，则不允许移动
        if (!binding.expression) {
          return
        }
        const classNameArr = ['gutter']
        setTimeout(() => {
          let tbodyBox = el.querySelector('thead')
          el.querySelectorAll(
            `.el-table__header-wrapper tr .${HeaderCellClassName}`
          ).forEach((item, index) => {
            item.setAttribute('draggable', true)
            item.setAttribute('index', index)
          })
          let curIndex = 0
          let toIndex = 0
          tbodyBox.addEventListener(
            'dragstart',
            function(e) {
              curIndex = e.target.getAttribute('index')
            },
            false
          )
          tbodyBox.addEventListener(
            'dragover',
            function(e) {
              toIndex = e.path
                .filter(item => item.nodeName === 'TH')[0]
                .getAttribute('index')
            },
            false
          )
          tbodyBox.addEventListener(
            'dragend',
            function(e) {
              binding.value({curIndex, toIndex})
            },
            false
          )
        }, 0)
      }
    }
  },
  mixins: [mixinOptionExtensions],
  props: {
    columns: {
      type: Array,
      default: () => []
    },
    data: {
      type: Array,
      default: () => []
    },
    editableMethod: {
      type: Function,
      default: () => {}
    }
  },
  data() {
    return {
      store: [],
      spanArr: [],
      spanObject: {},
      customSpanMethod: () => {}
    }
  },
  computed: {
    storeConfig() {
      return this.$store.state.tableHeadConfig
    },
    customColumns() {
      // 如果存在，则重新定义顺序，如果不存在，则直接返回当前columns
      if (this.storeConfig && this.storeConfig[this.$attrs['custom-attr']]) {
        let jsonArr = this.storeConfig[this.$attrs['custom-attr']]
        return jsonArr.map(item => {
          return this.columns[item]
        })
      } else {
        return this.columns
      }
    }
  },
  watch: {
    data: {
      handler: 'rowspan',
      immediate: true
    },
    columns: {
      handler: 'rowspan',
      immediate: true
    }
  },
  mounted() {
    this.customSpanMethod =
      this.$attrs['span-method'] ||
      this.$attrs.spanMethod ||
      this.arraySpanMethod
  },
  methods: {
    // 是否可以执行表头移动 ,'custonAttr', 'fixed'是属性
    checkTableHead(curIndex, toIndex) {
      let flag = true
      let lastColumns = this.customColumns
      // 选中的对象
      let curObj = lastColumns[curIndex]
      if (
        disableMoveTableArr.includes(curObj.type) ||
        curObj['customAttr'] ||
        curObj['fixed']
      ) {
        flag = false
      }
      // 目标对象
      let toObj = lastColumns[toIndex]
      if (
        disableMoveTableArr.includes(toObj.type) ||
        toObj['customAttr'] ||
        toObj['fixed']
      ) {
        flag = false
      }
      return flag
    },
    // 更改表头顺序
    changeTableHeadConfig({curIndex, toIndex}) {
      // 获取表头，查看表头是否可以更改
      if (this.checkTableHead(curIndex, toIndex)) {
        // 标识位置的更改
        let config = this.$store.state.tableHeadConfig
        let arrIndex
        if (config && config[this.$attrs['custom-attr']]) {
          arrIndex = cloneDeep(config[this.$attrs['custom-attr']])
        } else {
          arrIndex = new Array(this.customColumns.length)
            .fill('')
            .map((item, index) => index)
        }
        arrIndex.splice(toIndex, 0, ...arrIndex.splice(curIndex, 1))
        this.$store.commit('tableHeadConfig', {
          json: arrIndex,
          path: this.$attrs['custom-attr']
        })
      }
    },
    renderColumn(h, data = {}) {
      return data.columns.map((col, index) => {
        let columnIndex

        // todo: 待优化
        len = col.children ? len : len + 1 // 用于计算 column 的 index
        columnIndex = len - 1 // 从0开始，所以减1

        let filterData = {
          props: col
        }
        let scopedSlot =
          this.$scopedSlots[SLOT_DEFAULT + col.type] ||
          this.$scopedSlots[SLOT_DEFAULT + col.prop]

        if ((!col.type || TYPES.indexOf(col.type) < 0) && !scopedSlot) {
          filterData.scopedSlots = {
            default: scope => {
              const config =
                (data.editableMethod &&
                  data.editableMethod({
                    row: scope.row,
                    column: scope.column,
                    rowIndex: scope.$index,
                    columnIndex: columnIndex
                  })) ||
                col.render

              const renderElement =
                (typeof config === 'object' &&
                  (config.$component || config.$type)) ||
                'el-input'
              return config
                ? h(
                    renderElement,
                    {
                      props: Object.assign({}, config.$el, {
                        value: get(scope.row, col.prop)
                      }),
                      on: {
                        input: val => {
                          // 处理空值时修改很慢的问题
                          get(scope.row, col.prop) === undefined
                            ? this.$set(scope.row, col.prop, val) // todo: 这里存在bug, 如果 prop 为 . 拼接的 key
                            : set(scope.row, col.prop, val)
                        },
                        change: val => {
                          this.handleChange({
                            prop: col.prop,
                            value: val,
                            row: scope.row,
                            column: scope.column,
                            rowIndex: scope.$index,
                            columnIndex: columnIndex
                          })
                        }
                      }
                    },
                    [
                      (() => {
                        const optType =
                          config.$type && config.$type.indexOf('el-') === 0
                            ? config.$type.slice(3)
                            : config.$type
                        let optRenderer =
                          optType && this[`${toCamelCase(optType)}_opt`]
                        if (
                          typeof optRenderer === 'function' &&
                          Array.isArray(config.$options)
                        ) {
                          return config.$options.map(optRenderer)
                        }
                      })()
                    ]
                  )
                : col.formatter
                ? col.formatter(
                    scope.row,
                    scope.column,
                    get(scope.row, col.prop),
                    scope.$index
                  )
                : get(scope.row, col.prop)
            }
          }
        }
        if (scopedSlot) {
          filterData.scopedSlots = {
            default: scopedSlot
          }
        }
        if (this.$scopedSlots[SLOT_HEADER + col.prop]) {
          filterData.scopedSlots.header = this.$scopedSlots[
            SLOT_HEADER + col.prop
          ]
        }
        return h(
          'el-table-column',
          filterData,
          col.children && [
            this.renderColumn(h, {
              columns: col.children,
              editableMethod: this.editableMethod
            })
          ]
        )
      })
    },
    rowspan() {
      const data = this.data

      const getSpanObject = (arr, con = {}) => {
        return arr.reduce((sum, col) => {
          if (col.children && col.children.length) {
            getSpanObject(col.children, sum)
            return sum
          }

          if (!col.isMergeCell) {
            return sum
          }

          sum[col.prop] = []

          let position
          let spanArr = sum[col.prop]

          data.forEach((item, index) => {
            if (index === 0) {
              spanArr.push(1)
              position = 0
            } else {
              if (data[index][col.prop] === data[index - 1][col.prop]) {
                spanArr[position] += 1
                spanArr.push(0)
              } else {
                spanArr.push(1)
                position = index
              }
            }
          })
          return sum
        }, con)
      }

      this.spanObject = getSpanObject(this.columns)
    },
    arraySpanMethod({row, column, rowIndex, columnIndex}) {
      const spanArr = this.spanObject[column.property]
      if (spanArr) {
        const _row = spanArr[rowIndex]
        const _col = _row > 0 ? 1 : 0
        return {
          rowspan: _row,
          colspan: _col
        }
      }
    },
    handleChange(data) {
      const index = this.store.findIndex(
        item =>
          item.rowIndex === data.rowIndex &&
          item.columnIndex === data.columnIndex
      )
      this.$emit('cell-change', data)
      if (index > -1) {
        this.store.splice(index, 1, data)
        return
      }
      this.store.push(data)
    },
    setCellValue(key, rowIndex, value) {
      const row = this.data[rowIndex]
      set(row, key, value)
    },
    clearStore() {
      this.store = []
    },
    getValue() {
      return this.store
    }
  },
  render(h) {
    len = 0 // 初始化值

    const slots = Object.keys(this.$slots)
      .reduce((arr, key) => arr.concat(this.$slots[key]), [])
      // 手动更正 context
      .map(vnode => {
        vnode.context = this._self
        return vnode
      })
    const columns = this.renderColumn(h, {
      // 优先使用自定义表头
      columns: this.customColumns,
      editableMethod: this.editableMethod
    })

    return h(
      'el-table',
      {
        ref: 'table',
        props: Object.assign({}, this.$attrs, {
          data: this.data,
          rowClassName: RowClassName,
          border: true,
          headerCellClassName: HeaderCellClassName,
          spanMethod: this.customSpanMethod
        }),
        directives: [
          {
            name: 'drag',
            value: this.changeTableHeadConfig,
            expression: !!this.$attrs['custom-attr']
          }
        ],
        on: this.$listeners
      },
      columns.concat(slots)
    )
  }
}
</script>
<style lang="less">
.el-table {
  color: #26344c;
  border: 1px solid #f3f3f3;

  .editable-header-cell-class {
    height: 36px;
    padding: 5px 0;
    font-weight: 400;
    color: rgba(105, 109, 116, 1);
    background: rgba(236, 238, 243, 1);

    > div {
      cursor: pointer;
      width: 96%;
    }
  }

  .editable-row-class {
    height: 36px;

    > td {
      padding: 2px 0;
    }

    &:nth-child(odd):hover > td,
    &:nth-child(odd).hover-row > td {
      background-color: rgb(255, 255, 255);
    }

    &:nth-child(even) {
      background-color: rgb(248, 249, 251);

      &:hover > td,
      &.hover-row > td {
        background-color: rgb(248, 249, 251);
      }
    }
  }
}
</style>
